package rs.etf.kdp.workstation;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.JOptionPane;

import rs.ac.bg.etf.sleep.simulation.Event;
import rs.ac.bg.etf.sleep.simulation.SimBuffer;
import rs.etf.kdp.server.EventFromWS;
import rs.etf.kdp.server.Part;

public class SimBufferImplementation<V> implements SimBuffer<V> {

	private final Lock lock = new ReentrantLock();
	private final Condition full = lock.newCondition();
	private final Condition empty = lock.newCondition();
	private Object[] buffer;
	private int inBuffer, head, tail;
	private Part myPart;
	private HashMap<Long, Boolean> presentComponents;
	private int bufferSize;
	private ObjectOutputStream output;
	
	public SimBufferImplementation(Part myPart, int sizeOfBuffer, ObjectOutputStream output)
	{
		this.output = output;
		bufferSize = sizeOfBuffer;
		inBuffer = head = tail = 0;
		buffer = new Object[sizeOfBuffer];
		this.myPart = myPart;
		presentComponents = new HashMap<Long, Boolean>();
		List<String> componentsList = myPart.getComponents();
		
		//put all components in hash map
		for(String s : componentsList)
		{
			int firstSpace = s.indexOf(' ');
			String charId = s.substring(0, firstSpace);
			Long id = null;
			try {
				id = Long.parseLong(charId);
			} catch(NumberFormatException ne)
			{
				System.out.println("Number format exception in SimBufferLocal");
			}
			
			presentComponents.put(id, true);
		}
	}
	
	@Override
	public void putEvent(Event<V> event) {
		Long destID = event.getDstID();
		boolean isLocal;
		if(presentComponents.get(destID)!=null)
		{
			isLocal = true;
		} else
		{
			isLocal = false;
		}
		
		//determine weather put it in local buffer or sent it via socket
		if(isLocal)
		{
			putInLocalBuffer(event);
		} else
		{
			sendToOtherWorkstation(event);
		}
		
	}
	
	private void sendToOtherWorkstation(Event<V> event)
	{
		EventFromWS wsEvent = new EventFromWS(event, null);
		String message = "workstation transport " + event.getDstID() + " " + myPart.getServerId();
		
		try {
			output.writeObject(message);
			output.writeObject(wsEvent);
		} catch (IOException e) {
			System.out.println("Unable to write object in sentToOtherWorkstation");
		}
		
	}
	
	public void putInLocalBuffer(Event<V> event)
	{
		lock.lock();
		try {
			while(inBuffer >= bufferSize)
			{
				try {
					full.await();
				} catch (InterruptedException e) {
					System.out.println("InterruptedException in putInLocalBuffer");
				}
			}
			buffer[head] = event;
			head = (head + 1) % bufferSize;
			inBuffer++;
			empty.signal();
		} finally
		{
			lock.unlock();
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public void putEvents(List<Event<V>> events) {
		for(Event e : events)
		{
			putEvent(e);
		}
		
	}

	@SuppressWarnings("unchecked")
	@Override
	public Event<V> getEvent() {
		Event<V> e = null;
		lock.lock();
		try
		{
			while(inBuffer==0)
			{
				try {
					JOptionPane.showMessageDialog(null, "Empty buffer");
					empty.await();
				} catch (InterruptedException e1) {
					System.out.println("InterruptedException in putInLocalBuffer");
				}
			}
			e = (Event<V>) buffer[tail];
			tail = (tail + 1) % bufferSize;
			inBuffer--;
			full.signal();
		}finally
		{
			lock.unlock();
		}
		return e;
	}

	@Override
	public List<Event<V>> getEvents() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isEmpty() {
		if(inBuffer==0) return true;
		else return false;
	}

	@Override
	public long getMinrank() {
		// TODO Auto-generated method stub
		return 0;
	}

	public Part getMyPart() {
		return myPart;
	}

	public void setMyPart(Part myPart) {
		this.myPart = myPart;
	}

}
